package indigo.captcha.config;

import java.awt.Font;
import java.awt.FontFormatException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportAware;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternUtils;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.web.servlet.HandlerMapping;

import com.google.common.collect.Lists;
import com.octo.captcha.engine.image.ImageCaptchaEngine;
import com.octo.captcha.service.captchastore.CaptchaStore;
import com.octo.captcha.service.captchastore.FastHashMapCaptchaStore;
import com.octo.captcha.service.image.DefaultManageableImageCaptchaService;
import com.octo.captcha.service.image.ImageCaptchaService;

import indigo.captcha.ImageCaptchaController;
import indigo.captcha.ImageCaptchaEngineFactoryBean;
import indigo.core.DynamicRequestMappingHandlerMapping;

@Configuration
public class ImageCaptchaConfiguration implements ImportAware, ResourceLoaderAware {
	public static final int DEFAULT_TIME_TO_LIVE = 180;
	public static final int DEFAULT_MAX_STORE_SIZE = 100000;
	public static final int DEFAULT_WIDTH = 180;
	public static final int DEFAULT_HEIGHT = 70;
	public static final int DEFAULT_LENGTH = 4;
	public static final int DEFAULT_MAX_FONT_SIZE = 50;
	public static final boolean DEFAULT_CASE_SENSITIVE = false;
	public static final String DEFAULT_FONT_FILE_LOCATION = "classpath*:META-INF/captcha/*.ttf";
	public static final String DEFAULT_PATH = "/captcha";

	private ResourcePatternResolver resourcePatternResolver;

	private int timeToLive;
	private int maxStoreSize;
	private int width;
	private int height;
	private int length;
	private int maxFontSize;
	private boolean caseSensitive;
	private String path;
	private String[] fontFileLocations;

	@Override
	public void setImportMetadata(AnnotationMetadata importMetadata) {
		AnnotationAttributes attrs = AnnotationAttributes.fromMap(
				importMetadata.getAnnotationAttributes(EnableImageCaptcha.class.getName()));
		timeToLive = attrs.getNumber("timeToLive");
		maxStoreSize = attrs.getNumber("maxStoreSize");
		width = attrs.getNumber("width");
		height = attrs.getNumber("height");
		length = attrs.getNumber("length");
		maxFontSize = attrs.getNumber("maxFontSize");
		caseSensitive = attrs.getBoolean("caseSensitive");
		path = attrs.getString("path");
		fontFileLocations = attrs.getStringArray("fontFileLocations");
	}

	@Override
	public void setResourceLoader(ResourceLoader resourceLoader) {
		this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
	}

	private Font[] createFonts() {
		List<Font> fonts = Lists.newArrayList();
		try {
			for (String location : fontFileLocations) {
				for (Resource resource : resourcePatternResolver.getResources(location)) {
					try (InputStream stream = resource.getInputStream()) {
						fonts.add(Font.createFont(Font.TRUETYPE_FONT, stream));
					}
				}
			}
		} catch (FontFormatException e) {
			throw new IllegalStateException("Illegal font file format", e);
		} catch (IOException e) {
			throw new IllegalStateException("Failed to load font file", e);
		}
		return fonts.toArray(new Font[0]);
	}

	@Bean
	public ImageCaptchaEngineFactoryBean imageCaptchaEngine() {
		ImageCaptchaEngineFactoryBean factory = new ImageCaptchaEngineFactoryBean();
		factory.setWidth(width);
		factory.setHeight(height);
		factory.setLength(length);
		factory.setMaxFontSize(maxFontSize);
		factory.setFonts(createFonts());
		factory.setCaseSensitive(caseSensitive);
		return factory;
	}

	@Bean
	public CaptchaStore imageCaptchaStore() {
		return new FastHashMapCaptchaStore();
	}

	@Bean
	public ImageCaptchaService imageCaptchaService(ImageCaptchaEngine imageCaptchaEngine,
			@Qualifier("imageCaptchaStore") CaptchaStore captchaStore) {
		int storeLoad = (int) Math.round(0.8 * maxStoreSize);
		return new DefaultManageableImageCaptchaService(
				captchaStore, imageCaptchaEngine, timeToLive, maxStoreSize, storeLoad);
	}

	@Bean
	public ImageCaptchaController imageCaptchaController(ImageCaptchaService imageCaptchaService) {
		return new ImageCaptchaController(imageCaptchaService);
	}

	@Bean
	public HandlerMapping captchaHandlerMapping(ImageCaptchaController imageCaptchaController) {
		DynamicRequestMappingHandlerMapping handlerMapping = new DynamicRequestMappingHandlerMapping();
		handlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
		handlerMapping.setHandler(imageCaptchaController);
		handlerMapping.setPrefix(path);
		return handlerMapping;
	}
}
